using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.ObjectModel;

namespace Polymod
{
    public class ObservableCollectionModel<T> : ObservableCollection<IModel<T>>, IModelCollection<T>, INotify
    {
        object _value;

        //TODO: Good feature would be to have a settable array... (this way, Clear, Add, ect works)
        T[] _valueAsArray;
        IList<T> _valueAsList;
        ICollection<T> _valueAsCollection;

        InvokerRegistry _registry;
        IModelFactory _factory;
        IParentModelInfo _parent;
        readonly object _synclock = new object();
        IDictionary<string, object> _modelState;


        Action<int, T> _insertItem = (i, t) => { throw new NotSupportedException(); };
        Action<int> _removeItem = i => { throw new NotSupportedException(); };
        Action _clearItems = () => { throw new NotSupportedException(); };
        Action<int, T> _setItem = (i, t) => { throw new NotSupportedException(); };

        public void Initialize(object value, IDictionary<string, object> modelState)
        {
            if (value == null) throw new System.ArgumentNullException("value");
            _value = value;
            if (value.GetType().IsArray)
            {
                _valueAsArray = (T[])_value;
                _setItem = (i, t) => _valueAsArray[i] = t;
            }
            else if (typeof(IList<T>).IsAssignableFrom(value.GetType()))
            {
                _valueAsList = (IList<T>)_value;
                _setItem = (i, t) => _valueAsList[i] = t;
                _insertItem = (i, t) => _valueAsList.Insert(i, t);
                _removeItem = i => _valueAsList.RemoveAt(i);
                _clearItems = () => _valueAsList.Clear();
            }
            else if (typeof(ICollection<T>).IsAssignableFrom(value.GetType()))
            {
                _valueAsCollection = (ICollection<T>)_value;
                _insertItem = (i, t) => { if (i != _valueAsCollection.Count) throw new NotSupportedException(); _valueAsCollection.Add(t); };
                _removeItem = i => _valueAsCollection.Remove(_valueAsCollection.ElementAt(i));
                _clearItems = () => _valueAsCollection.Clear();
            }

            _registry = (InvokerRegistry)modelState[Constants.InvokerRegistry];
            _factory = (IModelFactory)modelState[Constants.ModelFactory];
            _parent = (IParentModelInfo)modelState[Constants.ParentModelInfo];
            _modelState = new Dictionary<string, object>(modelState);

            var enumerable = (IEnumerable<T>)value;
            foreach (var o in enumerable)
            {
                this.Items.Add((IModel<T>)_factory.GetModel(new ParentModelInfo(this, "[]"), o));
            }
        }

        public object Get(string name)
        {
            return _registry.GetValue(this, name);
        }

        public void Set(string name, object value)
        {
            _registry.SetValue(this, name, value);
        }

        public object GetValue()
        {
            return _value;
        }

        public void Add(T item)
        {
            //NOTE: No re-entrancy check here... all done in overrides.
            this.Add((IModel<T>)_factory.GetModel(new ParentModelInfo(this, "[]"), item));
        }

        private void CheckBaseReentrancy()
        {
#if !SILVERLIGHT
            base.CheckReentrancy();
#endif
        }

        #region Overrides
        protected override void ClearItems()
        {
            CheckBaseReentrancy();
            _clearItems();
            base.ClearItems();
        }
        protected override void InsertItem(int index, IModel<T> item)
        {
            CheckBaseReentrancy();
            _insertItem(index, item.GetValue());
            base.InsertItem(index, item);
        }
        protected override void RemoveItem(int index)
        {
            CheckBaseReentrancy();
            _removeItem(index);
            base.RemoveItem(index);
        }
        protected override void SetItem(int index, IModel<T> item)
        {
            CheckBaseReentrancy();
            _setItem(index, item.GetValue());
            base.SetItem(index, item);
        }

        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);
            ((INotify)this).NotifyChanged("[]");
        }
        #endregion

        public bool Contains(T item)
        {
            if (item == null) throw new System.ArgumentNullException("item");
            return AsModels.Any(o => item.Equals(o.GetValue()));
        }

        public ICollection<IModel<T>> AsModels
        {
            get
            {
                return this;
            }
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            for (int i = arrayIndex; i < this.Count && i < array.Length; i++)
            {
                array[i] = this[i].GetValue();
            }
        }

        public bool Remove(T item)
        {
            if (item == null) throw new System.ArgumentNullException("item");
            int index = 0;
            for (; index < this.Count; index++)
            {
                if (item.Equals(this[index])) break;
            }
            if (index >= this.Count) return false;
            this.RemoveAt(index);
            return true;
        }



        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets Synclock
        /// </summary>
        public object Synclock
        {
            get { return _synclock; }
        }

        /// <summary>
        /// Gets ModelState
        /// </summary>
        public IDictionary<string, object> ModelState
        {
            get { return _modelState; }
        }

        public new IEnumerator<T> GetEnumerator()
        {
            return AsModels.Select(m => m.GetValue()).GetEnumerator();
        }

        void INotify.NotifyChanged(string propertyName)
        {
            if (_parent != null)
            {
                var notifyParent = _parent.Parent as INotify;
                if (notifyParent != null)
                {
                    //TODO: Need to be clever about this... should notify parent.membername + '[]' -> BUT parent must not raise property '[]' changed!!!
                    notifyParent.NotifyChanged(_parent.MemberName);
                }
            }
        }

    }
}
